// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;

namespace SnapObjects.Data
{
    public abstract class AdoDbDataVisitor : IAdoDbDataVisitor
    {
        protected readonly DbCommand _command;
        protected readonly DbDataReader _reader;
        protected readonly IList<DbColumnInfo> _columnSchema;
        protected readonly List<IAdoDbParameter> _outputParameters;
        private bool _isDisposed = false;

        public int FieldCount => _reader.FieldCount;

        public bool HasRows => _reader.HasRows;

        public bool IsClosed => _reader.IsClosed;

        protected IDataContextOptions ContextOptions { get; }

        ~AdoDbDataVisitor()
        {
            this.Dispose();
        }

        protected AdoDbDataVisitor(IDbCommand command,
            IDataReader reader,
            IEnumerable<IAdoDbParameter> parameters,
            IDataContextOptions contextOptions)
        {
            _command = (DbCommand)command;
            _reader = (DbDataReader)reader;

            _outputParameters = new List<IAdoDbParameter>();

            if (parameters != null)
            {
                _outputParameters = parameters
                    .Where(m => m.Direction != ParameterDirection.Input)
                    .ToList();
            }

            _columnSchema = this.CreateColumnSchema();

            this.ContextOptions = contextOptions;
        }

        protected virtual IList<DbColumnInfo> CreateColumnSchema()
        {
            var columnSchema = new List<DbColumnInfo>();

            for (var i = 0; i < _reader.FieldCount; i++)
            {
                var columnInfo = new DbColumnInfo(
                    i,
                    _reader.GetName(i),
                    _reader.GetFieldType(i),
                    false,
                    false,
                    false,
                    false,
                    null,
                    null,
                    false,
                    _reader.GetDataTypeName(i),
                    0,
                    null,
                    false,
                    false,
                    0,
                    0,
                    false);

                columnSchema.Add(columnInfo);
            }

            return columnSchema;
        }

        public virtual IDataReader ReadData()
        {
            return _reader;
        }

        public IList<DbColumnInfo> GetColumnSchema()
        {
            return _columnSchema;
        }

        public virtual bool MoveNext()
        {
            if (_reader.IsClosed)
            {
                return false;
            }
            else
            {
                return _reader.Read();
            }
        }

        public virtual Task<bool> MoveNextAsync()
        {
            return this.MoveNextAsync(default);
        }

        public virtual Task<bool> MoveNextAsync(CancellationToken cancellationToken)
        {
            if (_reader.IsClosed)
            {
                return Task.FromResult(false);
            }

            return _reader.ReadAsync(cancellationToken);
        }

        public virtual bool NextResult()
        {
            return _reader.NextResult();
        }

        public virtual Task<bool> NextResultAsync(CancellationToken cancellationToken = default)
        {
            return _reader.NextResultAsync(cancellationToken);
        }

        public virtual IReadOnlyList<IAdoDbParameter> GetOutputParameters()
        {
            if (!_reader.IsClosed)
            {
                this.SetValueForOutputParameters();
            }

            return _outputParameters;
        }

        public virtual void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            _isDisposed = true;

            if (_reader != null)
            {
                if (!_reader.IsClosed)
                {
                    _reader.Close();

                    this.SetValueForOutputParameters();
                }

                _reader.Dispose();
            }

            if (_command != null)
            {
                _command.Cancel();
                _command.Dispose();
            }
        }

        public async virtual Task DisposeAsync()
        {
            if (_isDisposed)
            {
                return;
            }

            _isDisposed = true;

            if (_reader != null)
            {
                if (!_reader.IsClosed)
                {
                    await _reader.CloseAsync();

                    this.SetValueForOutputParameters();
                }

                await _reader.DisposeAsync();
            }

            if (_command != null)
            {
                _command.Cancel();

                await _command.DisposeAsync();
            }
        }

        protected virtual void SetValueForOutputParameters()
        {
            if (_outputParameters.Count < 1
               || _command == null
               || _command.Parameters.Count < 1)
            {
                return;
            }

            foreach (var parameter in _outputParameters)
            {
                if (_command.Parameters.Contains(parameter.ParameterName))
                {
                    ((AdoDbParameter)parameter).Value = _command.Parameters[parameter.ParameterName].Value;
                }
            }
        }
    }
}
